/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.rocketmq.namesrv;

import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.future.FutureTaskExt;
import org.apache.rocketmq.common.namesrv.NamesrvConfig;
import org.apache.rocketmq.common.utils.NetworkUtil;
import org.apache.rocketmq.common.utils.ThreadUtils;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.namesrv.kvconfig.KVConfigManager;
import org.apache.rocketmq.namesrv.processor.ClientRequestProcessor;
import org.apache.rocketmq.namesrv.processor.ClusterTestRequestProcessor;
import org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor;
import org.apache.rocketmq.namesrv.route.ZoneRouteRPCHook;
import org.apache.rocketmq.namesrv.routeinfo.BrokerHousekeepingService;
import org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager;
import org.apache.rocketmq.remoting.Configuration;
import org.apache.rocketmq.remoting.RemotingClient;
import org.apache.rocketmq.remoting.RemotingServer;
import org.apache.rocketmq.remoting.common.TlsMode;
import org.apache.rocketmq.remoting.netty.NettyClientConfig;
import org.apache.rocketmq.remoting.netty.NettyRemotingClient;
import org.apache.rocketmq.remoting.netty.NettyRemotingServer;
import org.apache.rocketmq.remoting.netty.NettyServerConfig;
import org.apache.rocketmq.remoting.netty.RequestTask;
import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.srvutil.FileWatchService;

public class NamesrvController {
  private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
  private static final Logger WATER_MARK_LOG = LoggerFactory.getLogger(LoggerName.NAMESRV_WATER_MARK_LOGGER_NAME);
  
  private final NamesrvConfig namesrvConfig;
  
  private final NettyServerConfig nettyServerConfig;
  private final NettyClientConfig nettyClientConfig;
  
  private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("NSScheduledThread").daemon(true).build());
  
  private final ScheduledExecutorService scanExecutorService = ThreadUtils.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("NSScanScheduledThread").daemon(true).build());
  
  private final KVConfigManager kvConfigManager;
  private final RouteInfoManager routeInfoManager;
  
  private RemotingClient remotingClient;
  private RemotingServer remotingServer;
  
  private final BrokerHousekeepingService brokerHousekeepingService;
  
  private ExecutorService defaultExecutor;
  private ExecutorService clientRequestExecutor;
  
  private BlockingQueue<Runnable> defaultThreadPoolQueue;
  private BlockingQueue<Runnable> clientRequestThreadPoolQueue;
  
  private final Configuration configuration;
  private FileWatchService fileWatchService;
  
  public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) {
    this(namesrvConfig, nettyServerConfig, new NettyClientConfig());
  }
  
  public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig, NettyClientConfig nettyClientConfig) {
    // 设置 name server 配置文件
    this.namesrvConfig = namesrvConfig;
    // 设置 server 配置文件
    this.nettyServerConfig = nettyServerConfig;
    // 设置 client 配置文件
    this.nettyClientConfig = nettyClientConfig;
    this.kvConfigManager = new KVConfigManager(this);
    this.brokerHousekeepingService = new BrokerHousekeepingService(this);
    // 路由信息管理
    this.routeInfoManager = new RouteInfoManager(namesrvConfig, this);
    // 配置文件的实例化
    this.configuration = new Configuration(LOGGER, this.namesrvConfig, this.nettyServerConfig);
    this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
  }
  
  public boolean initialize() {
    // 加载配置文件
    loadConfig();
    // 初期化 网络组件
    initiateNetworkComponents();
    // 初期化 线程池
    initiateThreadExecutors();
    // 注册处理器
    registerProcessor();
    // 启动定时器 通过定时器 来扫描Broker 是否存活
    startScheduleService();
    // 初期化 ssl上下文
    initiateSslContext();
    initiateRpcHooks();
    return true;
  }
  
  private void loadConfig() {
    this.kvConfigManager.load();
  }
  
  private void startScheduleService() {
    // 通过开启线程池  开启对 非激活Broker的扫描
    this.scanExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, this.namesrvConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS);
    
    this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, 1, 10, TimeUnit.MINUTES);
    
    this.scheduledExecutorService.scheduleAtFixedRate(() -> {
      try {
        NamesrvController.this.printWaterMark();
      } catch (Throwable e) {
        LOGGER.error("printWaterMark error.", e);
      }
    }, 10, 1, TimeUnit.SECONDS);
  }
  
  private void initiateNetworkComponents() {
    // 表示 netty server 端
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    // 表示 netty client 端
    this.remotingClient = new NettyRemotingClient(this.nettyClientConfig);
  }
  
  private void initiateThreadExecutors() {
    // 阻塞队列
    this.defaultThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getDefaultThreadPoolQueueCapacity());
    // 实例化 线程池
    this.defaultExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_"));
    
    this.clientRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getClientRequestThreadPoolQueueCapacity());
    this.clientRequestExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_"));
  }
  
  private void initiateSslContext() {
    if (TlsSystemConfig.tlsMode == TlsMode.DISABLED) {
      return;
    }
    
    String[] watchFiles = {TlsSystemConfig.tlsServerCertPath, TlsSystemConfig.tlsServerKeyPath, TlsSystemConfig.tlsServerTrustCertPath};
    
    FileWatchService.Listener listener = new FileWatchService.Listener() {
      boolean certChanged, keyChanged = false;
      
      @Override
      public void onChanged(String path) {
        if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
          LOGGER.info("The trust certificate changed, reload the ssl context");
          ((NettyRemotingServer) remotingServer).loadSslContext();
        }
        if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
          certChanged = true;
        }
        if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
          keyChanged = true;
        }
        if (certChanged && keyChanged) {
          LOGGER.info("The certificate and private key changed, reload the ssl context");
          certChanged = keyChanged = false;
          ((NettyRemotingServer) remotingServer).loadSslContext();
        }
      }
    };
    
    try {
      fileWatchService = new FileWatchService(watchFiles, listener);
    } catch (Exception e) {
      LOGGER.warn("FileWatchService created error, can't load the certificate dynamically");
    }
  }
  
  private void printWaterMark() {
    WATER_MARK_LOG.info("[WATERMARK] ClientQueueSize:{} ClientQueueSlowTime:{} " + "DefaultQueueSize:{} DefaultQueueSlowTime:{}", this.clientRequestThreadPoolQueue.size(), headSlowTimeMills(this.clientRequestThreadPoolQueue), this.defaultThreadPoolQueue.size(), headSlowTimeMills(this.defaultThreadPoolQueue));
  }
  
  private long headSlowTimeMills(BlockingQueue<Runnable> q) {
    long slowTimeMills = 0;
    final Runnable firstRunnable = q.peek();
    
    if (firstRunnable instanceof FutureTaskExt) {
      final Runnable inner = ((FutureTaskExt<?>) firstRunnable).getRunnable();
      if (inner instanceof RequestTask) {
        slowTimeMills = System.currentTimeMillis() - ((RequestTask) inner).getCreateTimestamp();
      }
    }
    
    if (slowTimeMills < 0) {
      slowTimeMills = 0;
    }
    
    return slowTimeMills;
  }
  
  private void registerProcessor() {
    // 测试集群的状态
    if (namesrvConfig.isClusterTest()) {
      
      this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()), this.defaultExecutor);
    } else {
      // 客户端 请求 处理器
      ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(this);
      // 注册  处理器
      this.remotingServer.registerProcessor(RequestCode.GET_ROUTEINFO_BY_TOPIC, clientRequestProcessor, this.clientRequestExecutor);
      // 注册 默认的处理器
      // DefaultRequestProcessor 是NameServer端 对外交户 窗口
      this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.defaultExecutor);
    }
  }
  
  private void initiateRpcHooks() {
    this.remotingServer.registerRPCHook(new ZoneRouteRPCHook());
  }
  
  public void start() throws Exception {
    this.remotingServer.start();
    
    // In test scenarios where it is up to OS to pick up an available port, set the listening port back to config
    if (0 == nettyServerConfig.getListenPort()) {
      nettyServerConfig.setListenPort(this.remotingServer.localListenPort());
    }
    
    this.remotingClient.updateNameServerAddressList(Collections.singletonList(NetworkUtil.getLocalAddress() + ":" + nettyServerConfig.getListenPort()));
    this.remotingClient.start();
    
    if (this.fileWatchService != null) {
      this.fileWatchService.start();
    }
    
    this.routeInfoManager.start();
  }
  
  public void shutdown() {
    this.remotingClient.shutdown();
    this.remotingServer.shutdown();
    this.defaultExecutor.shutdown();
    this.clientRequestExecutor.shutdown();
    this.scheduledExecutorService.shutdown();
    this.scanExecutorService.shutdown();
    this.routeInfoManager.shutdown();
    
    if (this.fileWatchService != null) {
      this.fileWatchService.shutdown();
    }
  }
  
  public NamesrvConfig getNamesrvConfig() {
    return namesrvConfig;
  }
  
  public NettyServerConfig getNettyServerConfig() {
    return nettyServerConfig;
  }
  
  public KVConfigManager getKvConfigManager() {
    return kvConfigManager;
  }
  
  public RouteInfoManager getRouteInfoManager() {
    return routeInfoManager;
  }
  
  public RemotingServer getRemotingServer() {
    return remotingServer;
  }
  
  public RemotingClient getRemotingClient() {
    return remotingClient;
  }
  
  public void setRemotingServer(RemotingServer remotingServer) {
    this.remotingServer = remotingServer;
  }
  
  public Configuration getConfiguration() {
    return configuration;
  }
}
